package cn.jingyuan.bee.utils.bean;

import cn.jingyuan.bee.utils.ClassUtils;
import cn.jingyuan.bee.utils.ReflectUtils;
import cn.jingyuan.bee.utils.clone.CloneSupport;
import cn.jingyuan.bee.utils.lang.Assert;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * 动态 Bean，通过反射对 Bean 的相关方法做操作，支持 Map 和普通 Bean。
 */
public class DynamicBean extends CloneSupport<DynamicBean> implements Serializable {

    private final Class<?> beanClass;

    private final Object bean;

    /**
     * 构造
     *
     * @param beanClass Bean 类
     * @param params 构造 Bean 所需要的参数
     */
    public DynamicBean(Class<?> beanClass, Object... params) {
        this(ReflectUtils.newInstance(beanClass, params));
    }

    /**
     * 构造
     *
     * @param bean 原始 Bean
     */
    public DynamicBean(Object bean) {
        Assert.notNull(bean);
        if (bean instanceof DynamicBean) {
            bean = ((DynamicBean) bean).getBean();
        }
        this.bean = bean;
        this.beanClass = ClassUtils.getClass(bean);
    }

    /**
     * 创建一个 {@link DynamicBean}
     *
     * @param bean 普通 Bean
     *
     * @return {@link DynamicBean}
     */
    public static DynamicBean create(Object bean) {
        return new DynamicBean(bean);
    }

    /**
     * 创建一个 {@link DynamicBean}
     *
     * @param beanClass Bean 类
     * @param params 构造 Bean 所需要的参数
     *
     * @return {@link DynamicBean}
     */
    public static DynamicBean create(Class<?> beanClass, Object... params) {
        return new DynamicBean(beanClass, params);
    }

    /**
     * 获得字段对应值
     *
     * @param <T> 属性值类型
     * @param fieldName 字段名
     *
     * @return 字段值
     *
     * @throws BeanException 反射获取属性值或字段值导致的异常
     */
    public <T> T get(String fieldName) throws BeanException {
        if (Map.class.isAssignableFrom(beanClass)) {
            return (T) ((Map<?, ?>) bean).get(fieldName);
        } else {
            try {
                final Method method = BeanUtils.getBeanDesc(beanClass).getGetter(fieldName);
                if (null == method) {
                    throw new BeanException("No get method for {}", fieldName);
                }
                return (T) method.invoke(this.bean);
            } catch (Exception e) {
                throw new BeanException(e);
            }
        }
    }

    /**
     * 获得字段对应值，获取异常返回 {@code null}
     *
     * @param <T> 属性值类型
     * @param fieldName 字段名
     *
     * @return 字段值
     */
    public <T> T safeGet(String fieldName) {
        try {
            return get(fieldName);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 设置字段值
     *
     * @param fieldName 字段名
     * @param value 字段值
     *
     * @throws BeanException 反射获取属性值或字段值导致的异常
     */
    public void set(String fieldName, Object value) throws BeanException {
        if (Map.class.isAssignableFrom(beanClass)) {
            ((Map) bean).put(fieldName, value);
        } else {
            try {
                final Method setter = BeanUtils.getBeanDesc(beanClass).getSetter(fieldName);
                if (null == setter) {
                    throw new BeanException("No set method for {}", fieldName);
                }
                setter.invoke(this.bean, value);
            } catch (Exception e) {
                throw new BeanException(e);
            }
        }
    }

    /**
     * 执行原始 Bean 中的方法
     *
     * @param methodName 方法名
     * @param params 参数
     *
     * @return 执行结果，可能为 null
     */
    public Object invoke(String methodName, Object... params) {
        return ReflectUtils.invoke(this.bean, methodName, params);
    }

    /**
     * 获得原始 Bean
     *
     * @param <T> Bean 类型
     *
     * @return bean
     */
    public <T> T getBean() {
        return (T) this.bean;
    }

    /**
     * 获得 Bean 的类型
     *
     * @param <T> Bean 类型
     *
     * @return Bean 类型
     */
    public <T> Class<T> getBeanClass() {
        return (Class<T>) this.beanClass;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((bean == null) ? 0 : bean.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final DynamicBean other = (DynamicBean) obj;
        if (bean == null) {
            return other.bean == null;
        } else {
            return bean.equals(other.bean);
        }
    }

    @Override
    public String toString() {
        return this.bean.toString();
    }

}
